﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace StrategyGame
{
    /// <summary>
    /// Represents one city on the map, keeping track of its stats, per-turn logic, construction projects and buildings
    /// </summary>
    class City: IMapObject
	{
		#region Static resources

		//see comment in Unit.cs about static variables/functions
		static private SpriteFont NameLabelFont;
		static private SpriteFont InfoLabelFont;

		static private Texture2D SmallCityTexture;

		static private Texture2D CityBackgroundTexture;

		static public void LoadContent(ContentManager content)
		{
			NameLabelFont = content.Load<SpriteFont>("fonts/BoldSmallFont");
			InfoLabelFont = content.Load<SpriteFont>("fonts/BoldSmallFont");

			SmallCityTexture = content.Load<Texture2D>("cities/city");
			CityBackgroundTexture = content.Load<Texture2D>("cities/city_background");
		}

		static List<IConstructionProject> ProjectsList = new List<IConstructionProject>();

		static City()
		{
			//initialize the list of projects that cities can work on
			ProjectsList.Add(new Building.ConstructionProject(Building.BuildingType.Farm));
			ProjectsList.Add(new Building.ConstructionProject(Building.BuildingType.Housing));

			ProjectsList.Add(new Building.ConstructionProject(Building.BuildingType.WallCorner));

			ProjectsList.Add(new Unit.ConstructionProject(Unit.Type.Cavalry));
			ProjectsList.Add(new Unit.ConstructionProject(Unit.Type.Infantry));
		}

		#endregion

		#region Public properties

		public Point Position { get; private set; }
        public string Name { get; private set; }

        public int Population { get; private set; }
        public int CityLevel { get; private set; }
        public int Culture { get; private set; }
        public int Food { get; private set; }
        public int LivingSpace { get; private set; }
        public int FreeWorkers { get; private set; }
		
		#endregion

		#region Construction AI

		enum ConstructionAutomation
        {
            /// <summary>
            /// Construct projects one at a time, switching to nothing after each one is completed
            /// </summary>
            Manual,
            
            /// <summary>
            /// Repeat the current project forever
            /// </summary>
            Repeat,

            /// <summary>
            /// Automatically pick projects, aiming to expand the population
            /// </summary>
            Expand,
        }

		#endregion

		#region Construction projects

		public IConstructionProject CurrentProject { get; private set; }
		public int ManpowerSpentOnProject { get; private set; }

		public int ManpowerPerTurn { get { return GovernmentManpowerPerTurn + PrivateManpowerPerTurn; } }
		public int GovernmentManpowerPerTurn { get; private set; }
		public int PrivateManpowerPerTurn
		{
			get
			{
				//for every point of city level, up to (100?) free workers will work on construction projects at no additional cost (the private sector)
				//todo: add a system of spending gold to increase this limit (hiring extra laborers)
				int free_manpower_per_point_of_citylevel = 100;

				return Math.Min(CityLevel * free_manpower_per_point_of_citylevel, FreeWorkers);
			}
		}

		//need this so we can check if production has changed since previous turn
		private IConstructionProject PreviousTurnProject;

		public enum ProjectSelectOption
		{
			Forward,
			Back,
			Cancel
		}

		public void SelectProject(ProjectSelectOption option)
		{
			int index_of_current_project = ProjectsList.IndexOf(CurrentProject);

			int index_of_new_project;

			if (index_of_current_project != -1)
			{
				switch (option)
				{
					case ProjectSelectOption.Back:
						index_of_new_project = index_of_current_project - 1;
						break;
					case ProjectSelectOption.Forward:
						index_of_new_project = index_of_current_project + 1;
						break;
					default: index_of_new_project = -1;
						break;
				}
			}
			else
			{
				switch (option)
				{
					case ProjectSelectOption.Back:
						index_of_new_project = ProjectsList.Count - 1;
						break;
					case ProjectSelectOption.Forward:
						index_of_new_project = 0;
						break;
					default: index_of_new_project = -1;
						break;
				}
			}

			if (index_of_new_project >= ProjectsList.Count)
				index_of_new_project = -1;

			if (index_of_new_project < 0)
				CurrentProject = null;
			else
				CurrentProject = ProjectsList[index_of_new_project];
		}

		#endregion

		#region Utility functions

		/// <summary>
		/// Gets a rectangle which encompasses this city and all its buildings
		/// </summary>
		/// <returns>A rectangle in map coordinates describing the position and size of the city on the map</returns>
		public Rectangle GetMapBoundsRect()
		{
			Point min = new Point(Position.X, Position.Y);
			Point max = new Point(Position.X, Position.Y);

			foreach (Building building in Buildings)
			{
				min.X = Math.Min(building.Position.X, min.X);
				min.Y = Math.Min(building.Position.Y, min.Y);

				max.X = Math.Max(building.Position.X, max.X);
				max.Y = Math.Max(building.Position.Y, max.Y);
			}

			return new Rectangle(min.X, min.Y, max.X - (min.X), max.Y - (min.Y));
		}

		public Point[] GetTilesAdjacentToBuildings()
		{
			HashSet<Point> result = new HashSet<Point>();

			//add the 8 tiles surrounding the city to the result
			Point[] city_adjacent_tiles = (Map.GetTilesAdjacentTo(Position.X, Position.Y));
			foreach (Point point in city_adjacent_tiles)
				result.Add(point);

			//do the same for each building the city owns
			foreach (Building building in Buildings)
			{
				Point[] building_adjacent_tiles = (Map.GetTilesAdjacentTo(building.Position.X, building.Position.Y));
				foreach (Point point in building_adjacent_tiles)
					result.Add(point);
			}

			return result.ToArray();
		}

		public static string PopulationToString(int population)
        {
            if (population >= 1000000)
                return "Metropolis";
            else if (population >= 100000)
                return "City";
            else if (population >= 10000)
                return "Town";
            else if (population >= 1000)
                return "Village";
            else
                return "Camp";
        }

		public Nation GetNation()
		{
			return Map.FindOwnerOfCity(this);
		}

		#endregion

		#region Implementation of IMapObject

		//which map we're on
		public TileMap Map { get; private set; }

		public bool BlocksUnitMovement { get { return true; } }
		public bool BlocksConstruction { get { return true; } }

		#endregion

		#region New turn calculations

		public int CalcuateGrowth()
		{
			int limiting_factor = Math.Min(LivingSpace, Food);

			if (Population < limiting_factor)
			{
				//the growth in population per turn is a percent of the difference between the current population and the lowest limiting factor
				int growth_percentage_of_difference_to_limiting_factor = 20;//%
				float growth_amount = (limiting_factor - Population) * ((float)growth_percentage_of_difference_to_limiting_factor / 100);

				//immigration! each 1 point of culture adds 10% to growth speed
				int culture_percent_bonus_per_point = 10;//%
				growth_amount += growth_amount * ((float)Culture * culture_percent_bonus_per_point) / 100;

				//always grow by at least one person if it's possible, so growth never COMPLETELY stops...
				growth_amount = Math.Max(1, growth_amount);

				return (int)growth_amount;
			}
			else
			{
				return 0;
			}
		}

		public void RemovePopulation(int population_loss)
		{
			Population = Math.Max(0, Population - population_loss);
		}

		public void UpdateTurn(int turn_number)
		{
			/* Order of the turn:
			 * -production: work is done on construction projects and finished buildings are added, so their stats can be taken into account this turn
			 * -growth: the city's population increases
			 * -recalculate stats
			 * -recalculate citylevel
			 */

			bool project_has_changed = (PreviousTurnProject != CurrentProject) && (PreviousTurnProject != null);

			if (project_has_changed)
			{
				ManpowerSpentOnProject = 0;
				PreviousTurnProject = null;
			}

			if (CurrentProject != null)
			{
				ManpowerSpentOnProject = Math.Min(ManpowerSpentOnProject + ManpowerPerTurn, CurrentProject.ManpowerRequired);

				//to finish the project it must not have any problems AND we must have met the manpower goal
				if (CurrentProject.GetProblemsBuildingThis(this) == null
					&& ManpowerSpentOnProject >= CurrentProject.ManpowerRequired)
				{
					CurrentProject.FinishProject(this);

					//reset this - blank slate
					ManpowerSpentOnProject = 0;
					PreviousTurnProject = null;
				}
				else
					PreviousTurnProject = CurrentProject;
			}

			//growth happens at the START of the turn, because it's based on the stats the city had the previous turn
			//living space and population are the two limiting factors on growth
			int limiting_factor = Math.Min(LivingSpace, Food);

			if (Population < limiting_factor)
			{
				Population += CalcuateGrowth();
				Population = Math.Min(limiting_factor, Population);
			}

			//stats are regenerated from scratch each turn based on the state of the city
			//they don't start at zero though, the town produces a small amount of food and living space by itself so it doesn't instantly die out
			Food = 200;
			LivingSpace = 300;
			Culture = 1;
			FreeWorkers = Population;

			foreach (Building building in Buildings)
			{
				FreeWorkers -= building.RequiredWorkers;

				switch (building.Type)
				{
					case Building.BuildingType.Farm:
						Food += 500;
						break;
					case Building.BuildingType.Housing:
						LivingSpace += 250;
						break;
				}
			}

			CityLevel = Math.Max(1, (Population / 1000) + Culture);
		}

		#endregion

		#region Rendering

		public void Draw(SpriteBatch sprites, Color nation_color, Nation pov_nation, int draw_offset_x, int draw_offset_y)
		{
			//get the tile rectangle to draw the city graphic in
			Rectangle city_rect = Map.GetTileRectangle(Position.X + draw_offset_x, Position.Y + draw_offset_y);

			//first things first, draw the city background no matter what
			sprites.Draw(CityBackgroundTexture, city_rect, nation_color);

			foreach (Building building in Buildings)
			{
				building.Draw(sprites, draw_offset_x, draw_offset_y, this);
			}
		}

		//this is separate so that we can ensure all text is drawn on top of all other graphics
		public void DrawTextLabels(SpriteBatch sprites, Color nation_color, Nation pov_nation, int draw_offset_x, int draw_offset_y)
		{
			//get the tile rectangle to draw the city graphic in
			Rectangle city_rect = Map.GetTileRectangle(Position.X + draw_offset_x, Position.Y + draw_offset_y);
			city_rect.Inflate(6, 6);

			string info_text;
			Color info_text_color;
			if (CurrentProject != null)
			{
				info_text = CurrentProject.ProjectName;
				info_text_color = Color.White;
			}
			else
			{
				info_text = "(no project)";
				info_text_color = Color.LightGray;
			}
			string name_text = Name + " - Lv." + CityLevel.ToString();

			Vector2 label_pos = TextUtils.GetPositionForAlignedText(NameLabelFont, name_text, city_rect, TextUtils.HorizontalAlignment.Middle, TextUtils.VerticalAlignment.Top);
			Vector2 poplabel_pos = TextUtils.GetPositionForAlignedText(InfoLabelFont, info_text, city_rect, TextUtils.HorizontalAlignment.Middle, TextUtils.VerticalAlignment.Bottom);

			//draw the city
			sprites.Draw(SmallCityTexture, city_rect, Color.White);

			//draw the drop shadow first so the name is on top of it
			Color dropshadow_color = Color.Black;

			TextUtils.DrawWithDropShadow(sprites, NameLabelFont, name_text, label_pos, Color.White, dropshadow_color, 1);
			TextUtils.DrawWithDropShadow(sprites, InfoLabelFont, info_text, poplabel_pos, info_text_color, dropshadow_color, 1);
		}

		#endregion

		#region This city's Buildings

		public List<Building> Buildings { get; private set; }

		public void AddBuilding(Building building)
		{
			Buildings.Add(building);
		}

		private void AddBuilding(Building.BuildingType new_building_type)
		{
			//temp: just put it above every time for now
			Point new_building_pos = new Point(Position.X, Position.Y - 1);

			Buildings.Add(new Building(new_building_type, new_building_pos, Map));
		}

		public Building BuildingAtTile(Point coordinates)
		{
			foreach (Building building in Buildings)
			{
				if (building.Position.Equals(coordinates))
					return building;
			}

			return null;
		}

		#endregion

		public City(TileMap on_map, Point position, string city_name)
        {
            Buildings = new List<Building>();

            Map = on_map;
            Map.AddMapObject(this);

            Position = position;

            Name = city_name;
            Population = 100;
            CityLevel = 0;
            Culture = 0;
            Food = 0;
            LivingSpace = 0;
            FreeWorkers = 0;

            ManpowerSpentOnProject = 0;
        }
    }
}
